<template>
  <dialog ref="dialogRef" class="formula-maintain-dialog">
    <form method="dialog" class="dialog-body" @submit.prevent="close">
      <div class="dialog-header">
        <h3>
          <svg class="title-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/>
            <path d="M9 10h6"/>
            <path d="M9 14h4"/>
          </svg>
          公式维护
        </h3>
        <button type="button" class="close-x" @click="close" aria-label="关闭">
          <svg class="close-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <line x1="18" y1="6" x2="6" y2="18"/>
            <line x1="6" y1="6" x2="18" y2="18"/>
          </svg>
        </button>
      </div>
      <!-- 新布局：左列参数，右列说明，下方公式表达式（倒三角） -->
      <div class="formula-grid">
        <!-- 左：参数列表 -->
        <div class="param-section" aria-label="参数列表">
          <div class="param-header">
            <h4>参数列表</h4>
            <div class="add-param">
              <input v-model="newParam" @keyup.enter.prevent="addParam" placeholder="参数名，如：产能" />
              <button type="button" class="btn small btn-add-param" @click="addParam" :disabled="!newParam.trim()">
                <svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                  <line x1="12" y1="5" x2="12" y2="19"/>
                  <line x1="5" y1="12" x2="19" y2="12"/>
                </svg>
                新增参数
              </button>
            </div>
          </div>
          <transition-group name="param-chip" tag="ul" class="param-list">
            <li v-for="p in params" :key="p" :class="{ 'just-added': p === lastAddedParam }">
              <span class="param-chip" @click="insertToken(p)">{{ p }}</span>
              <button type="button" class="icon-btn" title="删除" @click="removeParam(p)">
                <svg class="remove-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <line x1="18" y1="6" x2="6" y2="18"/>
                  <line x1="6" y1="6" x2="18" y2="18"/>
                </svg>
              </button>
            </li>
          </transition-group>
          <p v-if="showEmptyTip" class="empty-tip">暂无参数，先添加参数后可点击插入到公式。</p>
          <transition name="fade-slide"><p v-if="paramError" class="error-text">{{ paramError }}</p></transition>
        </div>
        <!-- 右：说明 -->
        <div class="field comment-field">
          <label><span class="field-title">说明</span>
            <textarea v-model="form.comment" rows="5" placeholder="可选：描述该公式含义，或对各参数的业务解释"></textarea>
          </label>
          <p class="hint mini">建议写清：参数含义、计量单位、计算目的。</p>
        </div>
        <!-- 下：公式表达式 -->
        <div class="field expression-field">
          <label><span class="field-title">公式表达式</span>
            <textarea ref="exprRef" v-model="form.expression" :disabled="!params.length" placeholder="例如：产能*100/人数" rows="4"></textarea>
          </label>
          <p class="hint">提示：
            <span v-if="!params.length">当前无参数，无法维护公式，需先添加至少两个参数。</span>
            <span v-else>点击上方参数即可插入。仅允许 + - * / () 及数字；需使用全部参数；单参数且无运算符不允许。</span>
          </p>
          <transition-group name="err-list" tag="ul" class="error-list" v-if="validationErrors.length">
            <li v-for="(err,i) in validationErrors" :key="err + i">{{ err }}</li>
          </transition-group>
        </div>
      </div>
      <div class="actions">
        <button type="button" class="btn" @click="close">
          <svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
            <path d="m6 9 6 6 6-6"/>
          </svg>
          取消
        </button>
        <button type="button" class="btn primary" :disabled="validationErrors.length>0" @click="save">
          <svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
            <polyline points="20,6 9,17 4,12"/>
          </svg>
          保存
        </button>
      </div>
    </form>
  </dialog>
</template>

<script setup lang="ts">
import { ref, computed, watch } from 'vue';

interface FormulaData { expression: string; comment?: string; params?: string[]; }

const dialogRef = ref<HTMLDialogElement | null>(null);
const form = ref<FormulaData>({ expression: '', comment: '', params: [] });
const params = ref<string[]>([]);
const newParam = ref('');
const exprRef = ref<HTMLTextAreaElement | null>(null);
const paramError = ref('');
// 记录最近一次新增的参数，用于高亮动画
const lastAddedParam = ref<string | null>(null);
// 控制空提示显示，避免直接切换导致最后一项删除动画不触发
const showEmptyTip = ref(false);

// 校验：
// 1) 公式仅包含：参数名(中英文/数字/下划线)、数字、小数点、+ - * / () 和空白
// 2) 所有参数必须在表达式中至少出现一次
// 3) 参数名不重复
const validationErrors = computed(() => {
  const errors: string[] = [];
  const expr = form.value.expression.trim();
  // 非法字符检查
  if (expr) {
    for (const ch of expr) {
      if (!/[A-Za-z0-9_\u4e00-\u9fa5+\-*/().\s]/.test(ch)) {
        errors.push(`包含非法字符: ${ch}`);
        break;
      }
    }
  }
  // 参数包含检查
  if (params.value.length) {
    const missing: string[] = [];
    for (const p of params.value) {
      const esc = p.replace(/[.*+?^${}()|[\]\\]/g, r => `\\${r}`);
      const reg = new RegExp(`(?<![A-Za-z0-9_\u4e00-\u9fa5])${esc}(?![A-Za-z0-9_\u4e00-\u9fa5])`);
      if (!reg.test(expr)) missing.push(p);
    }
    if (missing.length) errors.push(`以下参数未在公式中使用: ${missing.join(', ')}`);
  }
  // 未定义参数检测：提取表达式中所有可能的标识符，与参数列表对比
  if (expr) {
    const idents = expr.match(/[A-Za-z_\u4e00-\u9fa5][A-Za-z0-9_\u4e00-\u9fa5]*/g) || [];
    const unique = Array.from(new Set(idents));
    const undefinedOnes = unique.filter(id => !params.value.includes(id));
    if (undefinedOnes.length && params.value.length) {
      errors.push(`出现未定义参数: ${undefinedOnes.join(', ')}`);
    }
  }
  // 运算符两侧操作数与除以0检测
  if (expr) {
    const tokenPattern = /([A-Za-z_\u4e00-\u9fa5][A-Za-z0-9_\u4e00-\u9fa5]*|\d+(?:\.\d+)?|[()+\-*/])/g;
    const tokens = expr.match(tokenPattern) || [];
    const isOperand = (t: string) => /[A-Za-z_\u4e00-\u9fa5][A-Za-z0-9_\u4e00-\u9fa5]*/.test(t) || /\d+(?:\.\d+)?/.test(t) || t === ')' || t === '(';
    const isNumber = (t: string) => /^(?:\d+(?:\.\d+)?)$/.test(t);
    const operators = ['+','-','*','/'];
    if (tokens.length && tokens[0] && operators.includes(tokens[0] as string)) {
      errors.push('表达式不能以运算符开头');
    }
    if (tokens.length && tokens[tokens.length-1] && operators.includes(tokens[tokens.length-1] as string)) {
      errors.push('表达式不能以运算符结尾');
    }
    for (let i=0;i<tokens.length;i++) {
      const t = tokens[i];
      if (operators.includes(t)) {
        const left = tokens[i-1];
        const right = tokens[i+1];
        if (!left || !right) continue; // 已有开头/结尾检测
        // 左右两侧必须是参数/数字/括号(代表整体)，不允许连续运算符
        if (operators.includes(left)) {
          errors.push(`运算符 ${t} 左侧缺少操作数`);
          break;
        }
        if (operators.includes(right)) {
          errors.push(`运算符 ${t} 右侧缺少操作数`);
          break;
        }
        if (!isOperand(left)) {
          errors.push(`运算符 ${t} 左侧不是有效操作数`);
          break;
        }
        if (!isOperand(right)) {
          errors.push(`运算符 ${t} 右侧不是有效操作数`);
          break;
        }
        // 除以0检测（直接字面量0/0.0）
        if (t === '/' && isNumber(right) && parseFloat(right) === 0) {
          errors.push('检测到除以0的运算 /0');
          break;
        }
      }
    }
  }
  // 无参数时不允许维护公式（直接阻止保存）
  if (!params.value.length) {
    errors.push('当前无参数，不支持维护公式');
  }
  // 空表达式禁止保存（若有参数）
  if (!expr && params.value.length) errors.push('有参数时公式表达式不能为空');
  // 单参数且表达式仅为该参数本身（无运算符）不允许
  if (params.value.length === 1 && expr) {
    const onlyParam = params.value[0];
    const operatorPresent = /[+\-*/]/.test(expr);
    if (!operatorPresent && expr.replace(/\s+/g,'') === onlyParam) {
      errors.push('仅一个参数且未使用运算符，无需维护公式');
    }
  }
  return errors;
});

function open(initial?: Partial<FormulaData>) {
  form.value = { expression: initial?.expression || '', comment: initial?.comment || '', params: initial?.params || [] };
  params.value = [...(form.value.params || [])];
  dialogRef.value?.showModal();
  setTimeout(() => exprRef.value?.focus(), 50);
}
function close() { 
  const el = dialogRef.value; 
  if (!el) return; 
  // 添加关闭动画类
  el.classList.add('closing');
  // 等待过渡结束后真正关闭
  const done = () => { 
    el.removeEventListener('transitionend', done); 
    el.classList.remove('closing');
    el.close(); 
  };
  // 兜底：若 transitionend 未触发（某些浏览器或被打断），超时强制关闭
  setTimeout(done, 420);
  el.addEventListener('transitionend', done);
}
function save() {
  if (validationErrors.value.length) return;
  form.value.params = [...params.value];
  emit('save', { ...form.value });
  close();
}

function addParam() {
  paramError.value = '';
  const raw = newParam.value.trim();
  if (!raw) { paramError.value = '参数名不能为空'; return; }
  // 参数名只允许 中英文 / 数字 / 下划线
  if (!/^[A-Za-z0-9_\u4e00-\u9fa5]+$/.test(raw)) { paramError.value = '参数名仅允许中英文、数字或下划线'; return; }
  if (params.value.includes(raw)) { paramError.value = '参数名已存在'; return; }
  params.value.push(raw);
  lastAddedParam.value = raw;
  // 添加参数时隐藏空提示
  showEmptyTip.value = false;
  // 一段时间后移除高亮标记
  setTimeout(() => {
    if (lastAddedParam.value === raw) lastAddedParam.value = null;
  }, 1600);
  newParam.value='';
}
function removeParam(name: string) {
  const beforeLen = params.value.length;
  params.value = params.value.filter(p => p !== name);
  // 如果是删除最后一个，延迟显示空提示（等待离场动画 ~300ms）
  if (beforeLen === 1) {
    setTimeout(() => { if (!params.value.length) showEmptyTip.value = true; }, 320);
  }
}
function insertToken(token: string) {
  const el = exprRef.value;
  if (!el) return;
  const start = el.selectionStart;
  const end = el.selectionEnd;
  const v = form.value.expression || '';
  form.value.expression = v.slice(0, start) + token + v.slice(end);
  requestAnimationFrame(() => {
    el.focus();
    const pos = start + token.length;
    el.selectionStart = el.selectionEnd = pos;
  });
}

const emit = defineEmits<{(e:'save', payload: FormulaData):void}>();

// 当参数被全部移除时清空表达式，避免遗留无效内容
watch(params, (list) => {
  if (!list.length) {
    form.value.expression = '';
    // 如果外部直接清空，也延迟展示空提示
    setTimeout(() => { if (!params.value.length) showEmptyTip.value = true; }, 50);
  }
});

defineExpose({ open, close });
</script>

<style scoped>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');

* { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; }

.formula-maintain-dialog::backdrop { 
  background: rgba(15,23,42,.6); 
  backdrop-filter: blur(12px); 
  animation: backdropFadeIn 0.4s ease; 
}

@keyframes backdropFadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.formula-maintain-dialog { 
  --ease-silk: cubic-bezier(.4,0,.2,1); 
  /* 统一控制输入高度相关变量（便于再次微调） */
  --fm-input-py: 0.40rem; /* 原 0.55rem */
  --fm-input-px: 0.60rem; /* 原 0.7rem */
  --fm-input-font: 12.5px; /* 原 13px */
  border: none; 
  border-radius: 18px; 
  padding: 0; 
  width: 760px; /* 原 600px -> 加宽 */ 
  max-width: 95vw; 
  box-shadow: 0 20px 50px -14px rgba(15,23,42,.45), 0 8px 28px -10px rgba(15,23,42,.35); 
  overflow: hidden; 
  animation: dialogSlideIn .33s var(--ease-silk); 
  transition: opacity .26s var(--ease-silk), transform .3s var(--ease-silk), filter .3s var(--ease-silk); 
  will-change: transform, opacity, filter; 
}
.formula-maintain-dialog.closing { 
  opacity: 0; 
  transform: translateY(-9px) scale(.955); 
  filter: blur(3px) saturate(.88); 
  pointer-events: none; 
}
.formula-maintain-dialog::backdrop { transition: background-color .26s var(--ease-silk), backdrop-filter .3s var(--ease-silk); }
.formula-maintain-dialog.closing::backdrop { background: rgba(15,23,42,0); backdrop-filter: blur(0); }
@keyframes dialogSlideIn { 0% {opacity:0; transform: translateY(18px) scale(.93); filter: blur(5px);} 100% {opacity:1; transform: translateY(0) scale(1); filter: blur(0);} }

.dialog-body { 
  display: flex; 
  flex-direction: column; 
  gap: 0.85rem; 
  padding: 0.95rem 1.05rem 1rem; 
  background: linear-gradient(145deg, #ffffff, #f8fafc 60%, #f1f5f9); 
}

.dialog-header { 
  display: flex; 
  justify-content: space-between; 
  align-items: center; 
  padding-bottom: 0.9rem; /* 为光带留出一些空间 */
  border-bottom: none; 
  position: relative; 
}

/* 标题下方蓝色发光光带 */
.dialog-header::after { 
  content: ''; 
  position: absolute; 
  left: 6px; 
  right: 6px; 
  bottom: 0; 
  height: 2px; /* 更细 */ 
  background: linear-gradient(90deg, rgba(59,130,246,.65), rgba(99,102,241,.45) 45%, rgba(14,165,233,.5) 70%, rgba(59,130,246,.6)); /* 更淡 */ 
  border-radius: 4px; 
  box-shadow: 0 0 4px 1px rgba(59,130,246,.28), 0 0 8px 2px rgba(14,165,233,.2); /* 降低发光强度 */
  pointer-events: none; 
  backdrop-filter: blur(1px); 
}

.dialog-header h3 { 
  margin: 0; 
  font-size: 20px; 
  font-weight: 700; 
  letter-spacing: -0.025em; 
  color: #0f172a; 
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.title-icon {
  width: 24px;
  height: 24px;
  color: #3b82f6;
}

.close-x { 
  background: transparent; 
  border: none; 
  cursor: pointer; 
  padding: 0.5rem; 
  border-radius: 10px; 
  color: #6b7280; 
  transition: all 0.2s; 
  display: flex;
  align-items: center;
  justify-content: center;
}

.close-icon {
  width: 20px;
  height: 20px;
}

.close-x:hover { 
  background: linear-gradient(135deg, #fee2e2, #fecaca); 
  color: #dc2626;
  transform: rotate(90deg);
}

.close-x:active {
  transform: rotate(90deg) scale(0.9);
}

.field { 
  display: flex; 
  flex-direction: column; 
  gap: 0.45rem; 
  font-size: 12.5px; 
}

.field label {
  font-weight: 600;
  color: #374151;
}

.field input, .field textarea { 
  width: 100%; 
  padding: var(--fm-input-py) var(--fm-input-px); 
  border: 1px solid #d1d5db; 
  border-radius: 8px; /* 略微减小圆角以契合更紧凑的高度 */
  font-size: var(--fm-input-font); 
  resize: vertical; 
  background: #fff; 
  transition: all 0.22s cubic-bezier(0.4, 0, 0.2, 1);
  font-weight: 500; 
  line-height: 1.32; /* 略调以配合字号 */
  min-height: 34px; /* 保持可点击区域不至于过小 */
}

.field input:focus, .field textarea:focus {
  border-color: #3b82f6;
  box-shadow: 0 0 0 4px rgba(59,130,246,.1);
  outline: none;
  transform: translateY(-1px);
}

.actions { 
  display: flex; 
  justify-content: flex-end; 
  gap: 0.75rem; 
  margin-top: 0.3rem; 
  padding-top: 0.6rem; 
  border-top: 1px solid #e5e7eb;
}

.btn { 
  cursor: pointer; 
  border: 1px solid transparent; 
  padding: 0.5rem 1rem; 
  border-radius: 10px; 
  font-size: 13px; 
  font-weight: 600; 
  transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); 
  display: inline-flex; 
  align-items: center; 
  gap: 0.45rem;
}

.btn-icon {
  width: 16px;
  height: 16px;
}

.btn { 
  background: rgba(255,255,255,0.8); 
  border: 1px solid #e2e8f0; 
  color: #374151; 
}

.btn:hover {
  background: #f8fafc;
  transform: translateY(-1px);
}

.btn.primary { 
  background: linear-gradient(135deg, #3b82f6, #2563eb); 
  color: #fff; 
  border-color: #2563eb; 
}

.btn.primary:hover {
  background: linear-gradient(135deg, #2563eb, #1d4ed8);
  box-shadow: 0 6px 20px -4px rgba(59,130,246,.4);
}

.btn.small { 
  font-size: 11.5px; 
  padding: 0.4rem 0.65rem; 
  border-radius: 8px;
}

/* 新增参数按钮特别样式 */
.btn-add-param {
  background: #1e3a8a; /* 深蓝纯色保证对比 */
  background-image: linear-gradient(180deg, rgba(255,255,255,.06), rgba(0,0,0,.18));
  color: #ffffff;
  font-weight: 600;
  letter-spacing: 0.5px;
  border: 1px solid #1d4ed8;
  box-shadow: 0 1px 2px rgba(0,0,0,.4), 0 0 0 1px rgba(255,255,255,.08) inset;
  position: relative;
  overflow: hidden;
  text-shadow: 0 1px 2px rgba(0,0,0,.6);
}
.btn-add-param:hover {
  background: #1d4ed8;
  background-image: linear-gradient(180deg, rgba(255,255,255,.08), rgba(0,0,0,.12));
  box-shadow: 0 4px 10px -3px rgba(29,78,216,.55), 0 0 0 1px #2563eb inset;
  transform: translateY(-1px);
}
.btn-add-param:active {
  background: #1e40af;
  transform: translateY(0);
  box-shadow: 0 2px 6px -2px rgba(30,64,175,.65), 0 0 0 1px #1e3a8a inset;
}
.btn-add-param:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}
.btn-add-param:disabled {
  background: #475569;
  background-image: linear-gradient(180deg, rgba(255,255,255,.05), rgba(0,0,0,.15));
  border-color: #475569;
  color: #e2e8f0;
  text-shadow: none;
  box-shadow: none;
  cursor: not-allowed;
}
.btn-add-param:disabled .btn-icon { opacity: .55; }

.btn-add-param .btn-icon { transition: transform .35s cubic-bezier(.4,0,.2,1); }
.btn-add-param:hover .btn-icon { transform: rotate(90deg) scale(1.03); }
.btn-add-param:active .btn-icon { transform: rotate(90deg) scale(.9); }

.param-section { 
  display: flex; 
  flex-direction: column; 
  gap: 0.75rem; 
  border: 1px solid #e5e7eb; 
  padding: 0.9rem 1rem 0.85rem 1.05rem; /* 左侧略增 0.05rem 防止视觉裁切 */ 
  border-radius: 14px; 
  background: linear-gradient(135deg, #f8fafc, #f1f5f9); 
  position: relative;
}

/* 去除顶部彩条，保持简洁统一 */

.param-header { 
  display: flex; 
  justify-content: space-between; 
  align-items: center; 
  gap: 0.75rem; 
  flex-wrap: wrap;
}

.param-header h4 { 
  margin: 0; 
  font-size: 16px; 
  font-weight: 700; 
  color: #1f2937;
}

.add-param { 
  display: flex; 
  gap: 0.55rem; 
  align-items: center; 
  flex: 1; 
  min-width: 220px;
}

.add-param input { 
  flex: 1; 
  padding: calc(var(--fm-input-py) - 0.05rem) calc(var(--fm-input-px) - 0.05rem); 
  border: 1px solid #d1d5db; 
  border-radius: 7px; 
  font-size: 12px; 
  min-width: 0; 
  transition: var(--transition);
  line-height: 1.3;
  min-height: 32px;
}

.add-param input:focus {
  border-color: #3b82f6;
  box-shadow: 0 0 0 4px rgba(59,130,246,.1);
  outline: none;
  transform: translateY(-1px);
}

.add-param input:focus::placeholder {
  color: #9ca3af;
  transform: translateY(-2px);
}

.param-list { 
  list-style: none; 
  margin: 0; 
  padding: 4px 4px 6px 6px; /* 左右补内边距，防止最左/最右 chip 靠边裁切 */ 
  display: flex; 
  flex-wrap: wrap; 
  gap: 0.5rem; 
  max-height: 140px; 
  overflow-y: auto; 
  scrollbar-width: thin; 
  box-sizing: border-box;
}

.param-list li { 
  display: flex; 
  align-items: center; 
  gap: 0.35rem; 
  background: #fff; 
  border: 1px solid #e2e8f0; 
  padding: 0.35rem 0.55rem; 
  border-radius: 18px; 
  font-size: 12px; 
  box-shadow: 0 1px 2px rgba(0,0,0,.04); 
  transition: all 0.2s;
}

/* 新增项高亮动画：出现时加光晕 & 轻微放大后回弹 */
.param-list li.just-added {
  position: relative;
  background: linear-gradient(135deg,#eef6ff,#ffffff 45%);
  border-color: #93c5fd;
  box-shadow: 0 0 0 2px rgba(147,197,253,.6), 0 4px 14px -3px rgba(59,130,246,.45);
  animation: justAddedPop .75s cubic-bezier(.4,0,.2,1);
}
.param-list li.just-added::after {
  content: '';
  position: absolute;
  inset: -2px;
  border-radius: 20px;
  background: radial-gradient(circle at 30% 30%, rgba(255,255,255,.85), rgba(255,255,255,0) 70%);
  pointer-events: none;
  animation: fadeGlow 1.2s ease forwards;
}
@keyframes justAddedPop {
  0% { transform: scale(.6) translateY(14px); opacity: 0; filter: blur(4px); }
  55% { transform: scale(1.08) translateY(-2px); opacity: 1; filter: blur(0); }
  100% { transform: scale(1) translateY(0); opacity:1; }
}
@keyframes fadeGlow { 0% { opacity: 1; } 70% { opacity: .6; } 100% { opacity: 0; } }

/* 参数 chip 组动画 */
.param-chip-enter-from { opacity: 0; transform: translateY(10px) scale(.85); filter: blur(3px); }
.param-chip-enter-to { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
.param-chip-enter-active { transition: opacity .24s cubic-bezier(.4,0,.2,1), transform .32s cubic-bezier(.4,0,.2,1), filter .32s cubic-bezier(.4,0,.2,1); }
/* 离场淡出缩小：独立关键帧，强调消失的轻盈感 */
@keyframes chipFadeOut { 
  0% { opacity: 1; transform: scale(1) translateY(0); filter: blur(0); }
  60% { opacity: .50; transform: scale(.9) translateY(-4px); filter: blur(1px); }
  100% { opacity: 0; transform: scale(.72) translateY(-10px); filter: blur(3px); }
}
.param-chip-leave-active { animation: chipFadeOut .6s cubic-bezier(.4,0,.2,1) forwards; }
.param-chip-leave-from { opacity:1; }
.param-chip-leave-to { opacity:0; }
.param-chip-move { transition: transform .32s cubic-bezier(.4,0,.2,1); }

.param-list li:hover {
  border-color: #3b82f6;
  background: #f0f7ff;
  /* 使用双层阴影模拟描边 + 轻微浮起且不超出容器 */
  box-shadow: 0 0 0 2px rgba(59,130,246,.45), 0 2px 6px -1px rgba(59,130,246,.25);
  transform: none; /* 去除位移，彻底避免顶部裁切 */
}

.param-chip { 
  cursor: pointer; 
  font-weight: 600; 
  color: #374151; 
  user-select: none; 
  line-height: 1.1;
}

.param-chip:hover { 
  color: #2563eb; 
}

.param-chip:active {
  transform: scale(0.95);
}

.param-list li:hover .param-chip {
  animation: chipPulse 0.6s ease-in-out;
}

@keyframes chipPulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
}

.icon-btn { 
  cursor: pointer; 
  background: transparent; 
  border: none; 
  color: #dc2626; 
  line-height: 1; 
  padding: 0.2rem; 
  border-radius: 4px;
  transition: all 0.2s;
  display: flex;
  align-items: center;
  justify-content: center;
}

.remove-icon {
  width: 14px;
  height: 14px;
}

.icon-btn:hover { 
  color: #b91c1c; 
  background: #fee2e2;
}

.empty-tip { 
  font-size: 13px; 
  color: #6b7280; 
  margin: 0; 
  font-style: italic;
  text-align: center;
  padding: 1rem;
}

.hint { 
  margin: 0.5rem 0 0; 
  font-size: 12px; 
  color: #6b7280; 
  line-height: 1.5;
}

.error-text { 
  margin: 0.5rem 0 0; 
  font-size: 12px; 
  color: #dc2626; 
  font-weight: 500;
}

.error-list { 
  margin: 0.75rem 0 0; 
  padding-left: 1.2rem; 
  font-size: 12px; 
  color: #dc2626; 
  display: flex; 
  flex-direction: column; 
  gap: 0.3rem; 
  font-weight: 500;
}

/* 错误提示过渡：单条 paramError */
.fade-slide-enter-from, .fade-slide-leave-to { opacity: 0; transform: translateY(-4px); filter: blur(3px); }
.fade-slide-enter-to, .fade-slide-leave-from { opacity: 1; transform: translateY(0); filter: blur(0); }
.fade-slide-enter-active, .fade-slide-leave-active { transition: opacity .28s var(--ease-silk), transform .28s var(--ease-silk), filter .32s var(--ease-silk); }

/* 校验错误列表逐条动画 */
.err-list-enter-from { opacity: 0; transform: translateX(-8px) scale(.9); }
.err-list-enter-to { opacity: 1; transform: translateX(0) scale(1); }
.err-list-leave-from { opacity: 1; transform: translateX(0) scale(1); }
.err-list-leave-to { opacity: 0; transform: translateX(6px) scale(.85); }
.err-list-enter-active, .err-list-leave-active { transition: opacity .25s cubic-bezier(.4,0,.2,1), transform .3s cubic-bezier(.4,0,.2,1); }
.err-list-move { transition: transform .32s cubic-bezier(.4,0,.2,1); }

.btn.primary:disabled { 
  opacity: 0.5; 
  cursor: not-allowed; 
  background: #9ca3af;
  box-shadow: none;
}

/* 仅保留字段标题（说明 / 公式）样式，参数列表已恢复普通 h4 */
.field-title {
  display: inline-block;
  font-size: 13px;
  font-weight: 600;
  color: #1e293b;
  padding: 0;
  margin-bottom: 0.35rem;
}

/* 新增：倒三角网格布局 */
.formula-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-areas:
    "params comment"
    "expression expression";
  gap: 1rem 1.1rem;
}

.formula-grid .param-section { grid-area: params; }
.formula-grid .comment-field { grid-area: comment; }
.formula-grid .expression-field { grid-area: expression; }

@media (max-width: 860px) {
  .formula-grid {
    grid-template-columns: 1fr;
    grid-template-areas:
      "params"
      "comment"
      "expression";
    gap: 0.85rem;
  }
  .formula-grid .comment-field textarea { min-height: 100px; }
  .param-list { max-height: 120px; }
}

.comment-field textarea { min-height: 130px; resize: vertical; }
.expression-field textarea { font-family: 'JetBrains Mono', 'SFMono-Regular', monospace; min-height: 95px; }
.hint.mini { margin-top: 0.4rem; font-size: 11px; color: #94a3b8; }
</style>
